#include "pch.h"
#include "GameObject.h"

dxroids::GameObject::GameObject(Vector _pos, float _bounding_radius, float _scale)   
   : position_(_pos)
   , angle_of_rotation_(0.0f)   
   , rotational_velocity_(0.0f)
   , bounding_radius_(_bounding_radius * _scale)
   , scale_(_scale)
   , is_active_(false)
   , is_visible_(true)   
{
   
} 

dxroids::GameObject::~GameObject()
{

}

void dxroids::GameObject::Initialize(DXROIDS_COM_PTR<ID2D1Factory> const & _factory)
{
   geometry_ = CreateGeometry(_factory, scale_);
}

void dxroids::GameObject::UpdateFrame(
   GameTime const & _timer, 
   ClippingInformation const & _clip, 
   bool * _wrap_horizontal, 
   bool * _wrap_vertical)
{
   if (!is_active_)
   {
      return;
   }

#ifdef DXROIDS_DEBUG_COLLISIONS
   debug_render_flags_ = DebugRenderFlags::None;
#endif

   angle_of_rotation_ += rotational_velocity_;
   position_ += velocity_;

   bool did_wrap_vertical = false;
   bool did_wrap_horizontal = false;

   float const pos_x = position_.GetX();
   float const pos_y = position_.GetY();
   
   float const extent_x = bounding_radius_;
   if (pos_x > (_clip.x + extent_x))
   {
      position_.SetX(-extent_x);
      did_wrap_horizontal = true;      
   }
   else if (pos_x < -extent_x)
   {
       position_.SetX(extent_x + _clip.x);     
       did_wrap_horizontal = true;
   }

   float const extent_y = bounding_radius_;
   if (pos_y > (_clip.y + extent_y))
   {
      position_.SetY(-extent_y);
      did_wrap_vertical = true;
   }
   else if (pos_y < -extent_y)
   {
      position_.SetY(extent_y + _clip.y);
      did_wrap_vertical = true;
   }

   if (_wrap_horizontal)
   {
      *_wrap_horizontal = did_wrap_horizontal;
   }

   if (_wrap_vertical)
   {
      *_wrap_vertical = did_wrap_vertical;
   }

   if (registered_callbacks_.size() > 0)
   {
      std::vector<callback_list_type::iterator> to_remove;      
      for (callback_list_type::iterator begin = registered_callbacks_.begin(), end = registered_callbacks_.end();
         begin != end; ++begin)
      {
         if (begin->due_time < _timer.Elapsed)
         {         
            if (!begin->callback(_timer))
            {
               to_remove.push_back(begin);
            }
            else
            {
               begin->due_time = GameTime::SecondsToTicks(begin->interval) + GameTime::Now();;
            }
         }
      }   

      for (auto itr : to_remove)
      {
         registered_callbacks_.erase(itr);
      }
   }   
}

void dxroids::GameObject::Render(RenderContext const & _ctx)
{
   if (!is_active_ || !is_visible_)
   {
      return;
   }

   if (geometry_ == nullptr)
   {
      return;
   }
   
   _ctx.Target->SetTransform(GetTransform());
   _ctx.Target->DrawGeometry(geometry_, _ctx.PrimaryBrush);

   if ((_ctx.DebugFlags & DebugRenderFlags::DrawPosition) && _ctx.DebugBrush != nullptr)
   {     
      _ctx.Target->DrawEllipse(D2D1::Ellipse(D2D1::Point2F(), 5.0f, 5.0f), _ctx.DebugBrush);
   }

   if ((_ctx.DebugFlags & DebugRenderFlags::DrawStrechedRadius) && _ctx.DebugBrush != nullptr)
   {
      D2D1_ELLIPSE const ellipse = D2D1::Ellipse(D2D1::Point2F(), bounding_radius_, bounding_radius_);
      _ctx.Target->DrawEllipse(ellipse, _ctx.DebugBrush);      
   }
}

DXROIDS_COM_PTR<ID2D1Geometry> dxroids::GameObject::GetTransformedGeometry() const
{
   DXROIDS_COM_PTR<ID2D1Factory> factory;
   DXROIDS_COM_PTR<ID2D1TransformedGeometry> transformed;           

   geometry_->GetFactory(&factory);
   ThrowIfFailed(factory->CreateTransformedGeometry(geometry_, GetTransform(), &transformed));

   return DXROIDS_COM_PTR<ID2D1Geometry>(transformed);   
}


D2D1::Matrix3x2F dxroids::GameObject::GetTransform() const
{
   float const pos_x = position_.GetX();
   float const pos_y = position_.GetY();

   return 
      D2D1::Matrix3x2F::Translation(pos_x, pos_y) *
      D2D1::Matrix3x2F::Rotation(angle_of_rotation_, D2D1::Point2F(pos_x, pos_y));   
}
   
bool dxroids::GameObject::IntersectsWith(GameObject const & _rhs) const
{
   if (!IsActive() || !IsVisible())
   {
      return false;
   }

   if (!_rhs.IsActive() || !_rhs.IsVisible())
   {
      return false;
   }
   
   float const distance_square = GetPosition().DistanceSquared(_rhs.GetPosition());
   float const radius_squared = (BoundingRadius() + _rhs.BoundingRadius()) * (BoundingRadius() + _rhs.BoundingRadius());

   if (distance_square < radius_squared)
   {

#ifdef DXROIDS_DEBUG_COLLISIONS
      _rhs.SetDebugRenderFlags(dxroids::GameObject::DebugRenderFlags::DrawStrechedRadius);
      SetDebugRenderFlags(dxroids::GameObject::DebugRenderFlags::DrawStrechedRadius);
#endif

      return IntersectsWithGeometry(_rhs.GetTransformedGeometry());
   }
   
   return false;
}

bool dxroids::GameObject::IntersectsWithGeometry(DXROIDS_COM_PTR<ID2D1Geometry> const & _rhs) const
{   
   DXROIDS_COM_PTR<ID2D1Geometry> const lhs = GetTransformedGeometry();   
 
   D2D1_GEOMETRY_RELATION relation;
   ThrowIfFailed(lhs->CompareWithGeometry(_rhs, nullptr, &relation));
   DxRoidsAssert(relation != D2D1_GEOMETRY_RELATION_UNKNOWN);

   return relation != D2D1_GEOMETRY_RELATION_DISJOINT;   
}

DXROIDS_COM_PTR<ID2D1Geometry> dxroids::GameObject::CreateScaledPathGeometry(
   DXROIDS_COM_PTR<ID2D1Factory> const & _factory, std::vector<D2D1_POINT_2F> & _points, float _scale)
{
   DXROIDS_COM_PTR<ID2D1PathGeometry> path;
   DXROIDS_COM_PTR<ID2D1GeometrySink> sink;   

   ThrowIfFailed(_factory->CreatePathGeometry(&path));
   ThrowIfFailed(path->Open(&sink));

   int n = 0;
   for (D2D1_POINT_2F & p : _points)
   {
      D2D1_POINT_2F temp = p;
      temp.x *= _scale;
      temp.y *= _scale;
      
      if (n++ == 0)
      {
         sink->BeginFigure(temp, D2D1_FIGURE_BEGIN_FILLED);
      }
      else
      {
         sink->AddLine(temp);
      }
   }   
   sink->EndFigure(D2D1_FIGURE_END_CLOSED);
   ThrowIfFailed(sink->Close());

   DXROIDS_COM_PTR<ID2D1Geometry> geometry(path);
   return geometry;
}

 void dxroids::GameObject::RegisterTimedCallback(float _seconds, callback_function_type _func)
 {    
    callback_entry entry;
    entry.interval = _seconds;
    entry.due_time = GameTime::SecondsToTicks(entry.interval) + GameTime::Now();
    entry.callback = _func;    
    registered_callbacks_.push_back(entry);
 }
